#include <alsa/asoundlib.h>
#include <math.h>
#include <assert.h>
#include "logger.h"
#include "pcm-player.h"

// ALSA设备和参数相关变量
static snd_pcm_t *pcm_handle = NULL;
static snd_pcm_hw_params_t *params = NULL;
static snd_mixer_t *mixer_handle = NULL;
static snd_mixer_elem_t *mixer_elem = NULL;
static long min_volume = 0, max_volume = 0;

/**
 * 检查系统是否支持ALSA音频
 */
static int check_alsa_support() {
    snd_pcm_t *handle;
    int err;

    // Try to open default PCM device
    err = snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK, 0);
    if (err < 0) {
        dlog("ALSA not supported: %s\n", snd_strerror(err));
        return 0; // Not supported
    }

    snd_pcm_close(handle);
    dlog("ALSA is supported!\n");
    return 1; // Supported
}

/**
 * 初始化ALSA音频播放设备
 */
static int pcmPlayer_init(int channels, int samplerate, int bits_per_sample) {
    int rc;
    int rate = samplerate;
    snd_pcm_uframes_t buffer_size;
    snd_pcm_uframes_t period_size;
    int dir = 0;
    
    // Validate parameters
    if (channels <= 0 || samplerate <= 0 || bits_per_sample != 16) {
        elog("Invalid parameters: channels=%d, samplerate=%d, bits_per_sample=%d\n", 
             channels, samplerate, bits_per_sample);
        return -1;
    }

    // Open PCM device
    if ((rc = snd_pcm_open(&pcm_handle, "default", SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
        elog("snd_pcm_open error: %s\n", snd_strerror(rc));
        return -1;
    }

    // Allocate hardware parameters object
    if ((rc = snd_pcm_hw_params_malloc(&params)) < 0) {
        elog("snd_pcm_hw_params_malloc error: %s\n", snd_strerror(rc));
        snd_pcm_close(pcm_handle);
        pcm_handle = NULL;
        return -1;
    }

    // Fill hardware parameters with default values
    if ((rc = snd_pcm_hw_params_any(pcm_handle, params)) < 0) {
        elog("snd_pcm_hw_params_any error: %s\n", snd_strerror(rc));
        goto error_cleanup;
    }
    
    // Set access type to interleaved
    if ((rc = snd_pcm_hw_params_set_access(pcm_handle, params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
        elog("snd_pcm_hw_params_set_access error: %s\n", snd_strerror(rc));
        goto error_cleanup;
    }

    // Set sample format
    assert(bits_per_sample == 16);
    if ((rc = snd_pcm_hw_params_set_format(pcm_handle, params, SND_PCM_FORMAT_S16_LE)) < 0) {
        elog("snd_pcm_hw_params_set_format error: %s\n", snd_strerror(rc));
        goto error_cleanup;
    }

    // Set channel count
    if ((rc = snd_pcm_hw_params_set_channels(pcm_handle, params, channels)) < 0) {
        elog("snd_pcm_hw_params_set_channels error: %s\n", snd_strerror(rc));
        goto error_cleanup;
    }

    // Set sample rate
    if ((rc = snd_pcm_hw_params_set_rate_near(pcm_handle, params, &rate, 0)) < 0) {
        elog("snd_pcm_hw_params_set_rate_near error: %s\n", snd_strerror(rc));
        goto error_cleanup;
    }

    // Set buffer and period size
    buffer_size = samplerate / 2;  // 500ms buffer
    if ((rc = snd_pcm_hw_params_set_buffer_size_near(pcm_handle, params, &buffer_size)) < 0) {
        elog("snd_pcm_hw_params_set_buffer_size error: %s\n", snd_strerror(rc));
        goto error_cleanup;
    }
    
    period_size = 256;  // Reasonable default period size
    if ((rc = snd_pcm_hw_params_set_period_size_near(pcm_handle, params, &period_size, &dir)) < 0) {
        elog("snd_pcm_hw_params_set_period_size error: %s\n", snd_strerror(rc));
        goto error_cleanup;
    }
    
    // Apply hardware parameters
    if ((rc = snd_pcm_hw_params(pcm_handle, params)) < 0) {
        elog("snd_pcm_hw_params error: %s\n", snd_strerror(rc));
        goto error_cleanup;
    }

    // Get actual buffer and period size
    if ((rc = snd_pcm_hw_params_get_buffer_size(params, &buffer_size)) < 0) {
        elog("snd_pcm_hw_params_get_buffer_size error: %s\n", snd_strerror(rc));
        goto error_cleanup;
    }

    if ((rc = snd_pcm_hw_params_get_period_size(params, &period_size, &dir)) < 0) {
        elog("snd_pcm_hw_params_get_period_size error: %s\n", snd_strerror(rc));
        goto error_cleanup;
    }

    // Initialize mixer for volume control
    if ((rc = snd_mixer_open(&mixer_handle, 0)) < 0) {
        elog("snd_mixer_open error: %s\n", snd_strerror(rc));
        goto error_cleanup;
    }

    if ((rc = snd_mixer_attach(mixer_handle, "default")) < 0) {
        elog("snd_mixer_attach error: %s\n", snd_strerror(rc));
        snd_mixer_close(mixer_handle);
        mixer_handle = NULL;
        goto error_cleanup;
    }

    if ((rc = snd_mixer_selem_register(mixer_handle, NULL, NULL)) < 0) {
        elog("snd_mixer_selem_register error: %s\n", snd_strerror(rc));
        snd_mixer_close(mixer_handle);
        mixer_handle = NULL;
        goto error_cleanup;
    }

    if ((rc = snd_mixer_load(mixer_handle)) < 0) {
        elog("snd_mixer_load error: %s\n", snd_strerror(rc));
        snd_mixer_close(mixer_handle);
        mixer_handle = NULL;
        goto error_cleanup;
    }

    // Find mixer element (Master or PCM)
    snd_mixer_selem_id_t *sid;
    snd_mixer_selem_id_malloc(&sid);
    snd_mixer_selem_id_set_index(sid, 0);
    snd_mixer_selem_id_set_name(sid, "Master");

    mixer_elem = snd_mixer_find_selem(mixer_handle, sid);
    if (!mixer_elem) {
        snd_mixer_selem_id_set_name(sid, "PCM");
        mixer_elem = snd_mixer_find_selem(mixer_handle, sid);
        if (!mixer_elem) {
            elog("snd_mixer_find_selem error: Unable to find 'Master' or 'PCM' element\n");
            snd_mixer_close(mixer_handle);
            mixer_handle = NULL;
            snd_mixer_selem_id_free(sid);
            goto error_cleanup;
        }
    }

    snd_mixer_selem_id_free(sid);

    // Get volume range
    snd_mixer_selem_get_playback_volume_range(mixer_elem, &min_volume, &max_volume);

    ilog("Linux ALSA Audio player initialized\n");
    ilog("sndPcmHw - Buffer size: %lu, Period size: %lu frames\n", buffer_size, period_size);

    return 0;

error_cleanup:
    if (params) {
        snd_pcm_hw_params_free(params);
        params = NULL;
    }
    if (pcm_handle) {
        snd_pcm_close(pcm_handle);
        pcm_handle = NULL;
    }
    return -1;
}

/**
 * 获取当前PCM播放器参数
 */
static int pcmPlayer_getParams(int *channels, int *samplerate, int *bits_per_sample) {
    if(!pcm_handle || !params || !channels || !samplerate || !bits_per_sample) {
        elog("Invalid parameters or PCM handle not initialized\n");
        return -1;
    }
    
    int rc;
    snd_pcm_format_t format;
    int rate;

    if ((rc = snd_pcm_hw_params_get_channels(params, channels)) < 0) {
        elog("snd_pcm_hw_params_get_channels error: %s\n", snd_strerror(rc));
        return -1;
    }

    if ((rc = snd_pcm_hw_params_get_rate(params, &rate, 0)) < 0) {
        elog("snd_pcm_hw_params_get_rate error: %s\n", snd_strerror(rc));
        return -1;
    }
    *samplerate = rate;

    if ((rc = snd_pcm_hw_params_get_format(params, &format)) < 0) {
        elog("snd_pcm_hw_params_get_format error: %s\n", snd_strerror(rc));
        return -1;
    }
    *bits_per_sample = snd_pcm_format_physical_width(format);
    return 0;
}

/**
 * 设置音量（单位：分贝）
 */
static int pcmPlayer_setVolume(int db) {
    if ((!mixer_handle) || (!mixer_elem)) {
        elog("Mixer not initialized\n");
        return -1;
    }

    long vol;
    snd_mixer_selem_get_playback_volume(mixer_elem, SND_MIXER_SCHN_FRONT_LEFT, &vol);
    dlog("Current volume: %ld\n", vol);

    // Calculate new volume in dB
    long new_vol = vol * pow(10, db / 20.0);
    if (db > 0 && new_vol == vol) new_vol++; // Avoid zero gain

    // Clamp to valid range
    if (new_vol < min_volume) {
        new_vol = min_volume;
    } else if (new_vol > max_volume) {
        new_vol = max_volume;
    }

    dlog("Volume set to: %ld\n", new_vol);

    snd_mixer_selem_set_playback_volume_all(mixer_elem, new_vol);

    // Return normalized volume (0-100)
    if (max_volume == min_volume) {
        return 100;
    }
    return (int)((new_vol - min_volume) * 100 / (max_volume - min_volume));
}

/**
 * 向音频设备写入PCM数据
 */
static int pcmPlayer_write(void *pcm_data, int size) {
    if(!pcm_handle || !pcm_data || size <= 0) {
        elog("Invalid parameters or PCM handle not initialized\n");
        return -1;
    }
    
    snd_pcm_sframes_t rc;
    int channels, samplerate, bits_per_sample;
    
    if (pcmPlayer_getParams(&channels, &samplerate, &bits_per_sample) != 0) {
        return -1;
    }
    
    // Calculate frames to write
    snd_pcm_sframes_t frames = size / channels / (bits_per_sample / 8);
    
    // Write frames
    if ((rc = snd_pcm_writei(pcm_handle, pcm_data, frames)) < 0) {
        elog("snd_pcm_writei error: %s\n", snd_strerror(rc));
        
        // Handle underrun
        if(rc == -EPIPE) {
            // Recover from underrun
            if((rc = snd_pcm_recover(pcm_handle, -EPIPE, 0)) < 0) {
                elog("Failed to recover from underrun: %s\n", snd_strerror(rc));
                if((rc = snd_pcm_prepare(pcm_handle)) < 0) {
                    elog("Failed to prepare PCM device for playback: %s\n", snd_strerror(rc));
                    return -1;
                } else {
                    ilog("Prepared PCM device for playback\n");
                }
            } else {
                ilog("Recovered from underrun\n");
            }
            
            // Try writing again after recovery
            if ((rc = snd_pcm_writei(pcm_handle, pcm_data, frames)) < 0) {
                elog("snd_pcm_writei retry failed: %s\n", snd_strerror(rc));
                return -1;
            }
        } else {
            return -1;
        }
    }
    
    return 0;
}

/**
 * 控制PCM播放器（播放/暂停/停止）
 */
static int pcmPlayer_ctrl(player_ctrl_cmd_t cmd, void *arg) {
    if(!pcm_handle) {
        elog("PCM handle not initialized\n");
        return -1;
    }

    int rc;
    
    switch (cmd) {
        case PLAYER_CMD_PAUSE:
            if (snd_pcm_hw_params_can_pause(params)) {
                if ((rc = snd_pcm_pause(pcm_handle, 1)) < 0) {
                    elog("Failed to pause PCM playback: %s\n", snd_strerror(rc));
                    return -1;
                }
                dlog("PCM playback paused\n");
            } else {
                elog("PCM device does not support pause\n");
                return -1;
            }
            break;
            
        case PLAYER_CMD_RESUME:
            if (snd_pcm_hw_params_can_pause(params)) {
                if ((rc = snd_pcm_pause(pcm_handle, 0)) < 0) {
                    elog("Failed to resume PCM playback: %s\n", snd_strerror(rc));
                    return -1;
                }
                dlog("PCM playback resumed\n");
            } else {
                // If device doesn't support pause, try to prepare it for playback
                if ((rc = snd_pcm_prepare(pcm_handle)) < 0) {
                    elog("Failed to prepare PCM device for playback: %s\n", snd_strerror(rc));
                    return -1;
                }
                dlog("PCM device prepared for playback\n");
            }
            break;
            
        case PLAYER_CMD_STOP:
            if ((rc = snd_pcm_drop(pcm_handle)) < 0) {
                elog("Failed to stop PCM playback: %s\n", snd_strerror(rc));
                return -1;
            }
            if ((rc = snd_pcm_prepare(pcm_handle)) < 0) {
                elog("Failed to prepare PCM device after stop: %s\n", snd_strerror(rc));
                return -1;
            }
            dlog("PCM playback stopped\n");
            break;
            
        default:
            elog("Unknown command: %d\n", cmd);
            return -1;
    }

    return 0;
}

/**
 * 释放PCM播放器资源
 */
static int pcmPlayer_deinit() {
    int status = 0;

    if (pcm_handle) {
        if (snd_pcm_drain(pcm_handle) < 0) {
            elog("Failed to drain PCM device\n");
            status = -1;
        }
        if (snd_pcm_close(pcm_handle) < 0) {
            elog("Failed to close PCM device\n");
            status = -1;
        }
        pcm_handle = NULL;
    }

    if (params) {
        snd_pcm_hw_params_free(params);
        params = NULL;
    }

    if (mixer_handle) {
        if (snd_mixer_close(mixer_handle) < 0) {
            elog("Failed to close mixer\n");
            status = -1;
        }
        mixer_handle = NULL;
        mixer_elem = NULL;
    }

    dlog("Linux ALSA Audio player deinitialized\n");
    return status;
}

// PCM播放器接口结构体
static pcm_player_t pcm_player = {
    .platform = LINUX,
    .name = "ALSA",
    .check = check_alsa_support,
    .init = pcmPlayer_init,
    .getParams = pcmPlayer_getParams,
    .setVolume = pcmPlayer_setVolume,
    .write = pcmPlayer_write,
    .ctrl = pcmPlayer_ctrl,
    .deinit = pcmPlayer_deinit,
};

/**
 * 自动注册PCM播放器
 */
__attribute__((constructor))
static void auto_register_pcm_player(void) {
    pcmPlayer_register(&pcm_player);
}